package org.abratuhi.kvngroupware.pubsub;

import java.util.ArrayList;

import org.abratuhi.kvngroupware.pubsub.HubNode;
import org.abratuhi.kvngroupware.pubsub.Publication;
import org.abratuhi.kvngroupware.pubsub.Subscription;

public class Hub {
	public final static int MAX_SUB_PRO_ATTR_NODES = 5;
	public final static int OVERLOAD_FACTOR = 2;
	
	public String attribute = new String();
	public ArrayList<HubNode> attributeNodes = new ArrayList<HubNode>();
	
	public Hub(String attribute){
		this.attribute = attribute;
	}
	
	public void addSubscription(Subscription sub){
		//
		if(attributeNodes.size() == 0){
			attributeNodes.add(new HubNode(attribute, sub.minOf(attribute), sub.maxOf(attribute)));
		}
		else if(!sub.within(attribute, attributeNodes.get(0).minimum, attributeNodes.get(attributeNodes.size()-1).maximum)){
			if(sub.intersects(attribute, attributeNodes.get(0).minimum, attributeNodes.get(attributeNodes.size()-1).maximum)){
				if(sub.extL(attribute, attributeNodes.get(0).minimum, attributeNodes.get(attributeNodes.size()-1).maximum)){
					attributeNodes.add(0, new HubNode(attribute, sub.minOf(attribute), attributeNodes.get(0).minimum));
				}
				else if(sub.extR(attribute, attributeNodes.get(0).minimum, attributeNodes.get(attributeNodes.size()-1).maximum)){
					attributeNodes.add(new HubNode(attribute, attributeNodes.get(attributeNodes.size()-1).maximum, sub.maxOf(attribute)));
				}
			}
			else{
				if(sub.outL(attribute, attributeNodes.get(0).minimum, attributeNodes.get(attributeNodes.size()-1).maximum)){
					attributeNodes.add(0, new HubNode(attribute, sub.minOf(attribute), attributeNodes.get(0).minimum));
				}
				else if(sub.outR(attribute, attributeNodes.get(0).minimum, attributeNodes.get(attributeNodes.size()-1).maximum)){
					attributeNodes.add(new HubNode(attribute, attributeNodes.get(attributeNodes.size()-1).maximum, sub.maxOf(attribute)));
				}
				
			}
		}
		//
		for(int i=0; i<attributeNodes.size(); i++) if(sub.intersects(attribute, attributeNodes.get(i).minimum, attributeNodes.get(i).maximum)) attributeNodes.get(i).addSubscription(sub);
		for(int i=0; i<attributeNodes.size(); i++) if(needsBalancing(i)) performBalanceLoadOn(i);
		//while(getFirstUnbalanced() != -1) performBalanceLoadOn(getFirstUnbalanced());
	}
	
	public void removeSubscription(Subscription sub){
		// TODO
	}
	
	public void removeSubscription(String subscriptionId){
		for(int i=0; i<attributeNodes.size(); i++) attributeNodes.get(i).removeSubscription(subscriptionId);
	}
	
	public void removeSubscriptions(String subscriberId){
		for(int i=0; i<attributeNodes.size(); i++) attributeNodes.get(i).removeSubscriptions(subscriberId);
	}
	
	public ArrayList<Subscription> getSubscriptions(String subscriberId){
		ArrayList<Subscription> result = new ArrayList<Subscription>();
		for(int i=0; i<attributeNodes.size(); i++) result.addAll(attributeNodes.get(i).getSubscriptions(subscriberId));
		return result;
	}
	
	public Subscription getSubscription(String subscriptionId){
		for(int i=0; i<attributeNodes.size(); i++) if(attributeNodes.get(i).getSubscription(subscriptionId)!=null) return attributeNodes.get(i).getSubscription(subscriptionId);
		return null;
	} 
	
	public void updateSubscription(){
		// TODO
	}
	
	public boolean needsBalancing(){
		for(int i=0; i<attributeNodes.size(); i++){
			if(attributeNodes.get(i).subscriptions.size() > MAX_SUB_PRO_ATTR_NODES) return true;
			else if(attributeNodes.size() > 1 && i == 0 && attributeNodes.get(i+1).subscriptions.size()*OVERLOAD_FACTOR < attributeNodes.get(i).subscriptions.size()) return true;
			else if(attributeNodes.size() > 1 && i > 0 && attributeNodes.get(i-1).subscriptions.size()*OVERLOAD_FACTOR < attributeNodes.get(i).subscriptions.size()) return true;
		}
		return false;
	}
	
	public boolean needsBalancing(HubNode hn){
		int index = attributeNodes.indexOf(hn);
		if(hn.subscriptions.size() > MAX_SUB_PRO_ATTR_NODES) return true;
		else if(attributeNodes.size() > 1 && index == 0 && attributeNodes.get(index+1).subscriptions.size()*OVERLOAD_FACTOR < attributeNodes.get(index).subscriptions.size()) return true;
		else if(attributeNodes.size() > 1 && index > 0 && attributeNodes.get(index-1).subscriptions.size()*OVERLOAD_FACTOR < attributeNodes.get(index).subscriptions.size()) return true;
		else return false;
	}
	
	public boolean needsBalancing(int index){
		HubNode hn = attributeNodes.get(index);
		return needsBalancing(hn);
	}
	
	public int getFirstUnbalanced(){
		int n = -1;
		for(int i=0; i<attributeNodes.size(); i++){
			if(attributeNodes.get(i).subscriptions.size() > MAX_SUB_PRO_ATTR_NODES) n = i;
			else if(attributeNodes.size() > 1 && i == 0 && attributeNodes.get(i+1).subscriptions.size()*OVERLOAD_FACTOR < attributeNodes.get(i).subscriptions.size()) n = i;
			else if(attributeNodes.size() > 1 && i > 0 && attributeNodes.get(i-1).subscriptions.size()*OVERLOAD_FACTOR < attributeNodes.get(i).subscriptions.size()) n = i;
		}
		return n;
	}
	
	public void performBalanceLoadOn(int index){
		if(attributeNodes.size() == 1){	// single node -> split
			HubNode[] nHNs = HubNode.split(attributeNodes.get(index));
			attributeNodes.remove(index);
			attributeNodes.add(nHNs[0]);
			attributeNodes.add(nHNs[1]);
		}
		else{	// more than 1 nodes
			int indexh = (index > 0) ? index-1 : index+1;
			HubNode hnOverloaded = attributeNodes.get(index);
			HubNode hnHelper = attributeNodes.get(indexh);
			if(needsBalancing(hnHelper)){	// neighbor node needs balancing itself -> split current
				System.out.println("balancing (splitting) "+attributeNodes.get(index).toString());
				HubNode[] nHNs = HubNode.split(hnOverloaded);
				attributeNodes.set(index, nHNs[1]);
				attributeNodes.add(index, nHNs[0]);
			}
			else{	// neighbor node doesn't need balancing -> balance
				HubNode[] nhn  = null;
				if(indexh<index){
					nhn = HubNode.balanceToLeft(hnOverloaded, hnHelper);
				}
				else{
					nhn = HubNode.balanceToRight(hnOverloaded, hnHelper);
				}
				attributeNodes.set(index, nhn[0]);
				attributeNodes.set(indexh, nhn[1]);
			}
		}
	}
	

	public ArrayList<Subscription> match(Publication pub){
		ArrayList<Subscription> result = new ArrayList<Subscription>();
		for(int i=0; i<attributeNodes.size(); i++) result.addAll(attributeNodes.get(i).match(pub));
		return result;
	}
	
	public String toString(){
		String result = "hub: " + attribute + "\n";
		for(int i=0; i<attributeNodes.size(); i++) result += attributeNodes.get(i).toString();
		return result;
	}
}
